package com.i2works.smartluolong.utils.shiro.filter;


import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.i2works.smartluolong.utils.base.MsgEnum;
import com.i2works.smartluolong.utils.base.ShiroConstants;
import com.i2works.smartluolong.utils.entity.R;
import com.i2works.smartluolong.utils.entity.Result;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * 无状态授权拦截器
 *
 * @author koal <koal@vip.qq.com>
 * @date 2018/5/14 15:49
 */
public class StatelessAuthcFilter extends AccessControlFilter {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }


    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //1、客户端生成的消息摘要
        String clientDigest = request.getHeader(ShiroConstants.APP_DIGEST);
        //2、客户端传入的用户Name
        String userName = request.getHeader(ShiroConstants.APP_USERNAME);
        //3、客户端传入的用户ID
        String userId = request.getHeader(ShiroConstants.APP_USERID);
        //4、客户端传入的时间戳
        String timestamp = request.getHeader(ShiroConstants.APP_TIMESTAMP);
        //5、客户端请求的参数列表
        Map<String, String[]> params = Maps.newLinkedHashMap(request.getParameterMap());
        params.remove(ShiroConstants.APP_DIGEST);
        if (timestamp == null || timestamp.length() != 13) {
            //时间格式错误
            onParamsError(servletResponse);
            return false;
        }
        //如果包含phone的参数，说明是手机短信，即可无需判断用户ID和用户名
        if (request.getParameterMap().containsKey("phone")) {
            if (StringUtils.isAnyBlank(clientDigest, timestamp)) {
                onParamsError(servletResponse);
                return false;
            }
        } else {
            if (StringUtils.isAnyBlank(clientDigest, userName, userId, timestamp)) {
                onParamsError(servletResponse);
                return false;
            }
        }
        logger.error("客户端的摘要：" + clientDigest);
        //生成无状态Token
        StatelessToken token = new StatelessToken(userName, Integer.parseInt(userId), Long.parseLong(timestamp), params, clientDigest);
        if (!differ(Long.parseLong(timestamp))) {
            //鉴权超时
            onExpTimeFail(servletResponse);
            return false;
        }
        try {
            //委托给Realm进行登录
            getSubject(servletRequest, servletResponse).login(token);
        } catch (Exception e) {
            //登录失败
            onLoginFail(servletResponse);
            return false;
        }
        return true;
    }

    //超时返回错误
    private void onExpTimeFail(ServletResponse response) throws IOException {
        doWriter(response, MsgEnum.ACCESS_AUTHC_EXPTIME.getMsg());
    }

    //登录失败时默认返回401状态码
    private void onParamsError(ServletResponse response) throws IOException {
        doWriter(response, MsgEnum.ACCESS_AUTHC_PARAMS_ERROR.getMsg());
    }

    //登录失败时默认返回401状态码
    private void onLoginFail(ServletResponse response) throws IOException {
        doWriter(response, MsgEnum.ACCESS_AUTHC_FAIL.getMsg());
    }

    private void doWriter(ServletResponse response, String message) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json; charset=utf-8");
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        Result error = R.error(message);
        error.setErrorCode(HttpServletResponse.SC_UNAUTHORIZED);
        httpResponse.getWriter().write(JSON.toJSONString(error));
    }

    /**
     * 验证鉴权时间的时间差
     *
     * @see 客户端发送的时间 与  当前系统时间 进行对比
     * @see 两者相差 <=300秒，否则鉴权失败
     */
    private boolean differ(Long timestamp) {
        return ((System.currentTimeMillis() - timestamp) / 1000) <= ShiroConstants.APP_EXP_TIME;
    }


}
